{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "#| eval: false\n",
    "! [ -e /content ] && pip install -Uqq fastai  # upgrade fastai on colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "from __future__ import annotations\n",
    "from fastai.basics import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| default_exp callback.progress"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Progress and logging\n",
    "\n",
    "> Callback and helper function to track progress of training or log results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fastai.test_utils import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ProgressCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@docs\n",
    "class ProgressCallback(Callback):\n",
    "    \"A `Callback` to handle the display of progress bars\"\n",
    "    order,_stateattrs = 60,('mbar','pbar')\n",
    "\n",
    "    def before_fit(self):\n",
    "        assert hasattr(self.learn, 'recorder')\n",
    "        if self.create_mbar: self.mbar = master_bar(list(range(self.n_epoch)))\n",
    "        if self.learn.logger != noop:\n",
    "            self.old_logger,self.learn.logger = self.logger,self._write_stats\n",
    "            self._write_stats(self.recorder.metric_names)\n",
    "        else: self.old_logger = noop\n",
    "\n",
    "    def before_epoch(self):\n",
    "        if getattr(self, 'mbar', False): self.mbar.update(self.epoch)\n",
    "\n",
    "    def before_train(self):    self._launch_pbar()\n",
    "    def before_validate(self): self._launch_pbar()\n",
    "    def after_train(self):     self.pbar.on_iter_end()\n",
    "    def after_validate(self):  self.pbar.on_iter_end()\n",
    "    def after_batch(self):\n",
    "        self.pbar.update(self.iter+1)\n",
    "        if hasattr(self, 'smooth_loss'): self.pbar.comment = f'{self.smooth_loss.item():.4f}'\n",
    "\n",
    "    def _launch_pbar(self):\n",
    "        self.pbar = progress_bar(self.dl, parent=getattr(self, 'mbar', None), leave=False)\n",
    "        self.pbar.update(0)\n",
    "\n",
    "    def after_fit(self):\n",
    "        if getattr(self, 'mbar', False):\n",
    "            self.mbar.on_iter_end()\n",
    "            delattr(self, 'mbar')\n",
    "        if hasattr(self, 'old_logger'): self.learn.logger = self.old_logger\n",
    "\n",
    "    def _write_stats(self, log):\n",
    "        if getattr(self, 'mbar', False): self.mbar.write([f'{l:.6f}' if isinstance(l, float) else str(l) for l in log], table=True)\n",
    "\n",
    "    _docs = dict(before_fit=\"Setup the master bar over the epochs\",\n",
    "                 before_epoch=\"Update the master bar\",\n",
    "                 before_train=\"Launch a progress bar over the training dataloader\",\n",
    "                 before_validate=\"Launch a progress bar over the validation dataloader\",\n",
    "                 after_train=\"Close the progress bar over the training dataloader\",\n",
    "                 after_validate=\"Close the progress bar over the validation dataloader\",\n",
    "                 after_batch=\"Update the current progress bar\",\n",
    "                 after_fit=\"Close the master bar\")\n",
    "\n",
    "if not hasattr(defaults, 'callbacks'): defaults.callbacks = [TrainEvalCallback, Recorder, ProgressCallback]\n",
    "elif ProgressCallback not in defaults.callbacks: defaults.callbacks.append(ProgressCallback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>14.523648</td>\n",
       "      <td>10.988108</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>12.395808</td>\n",
       "      <td>7.306935</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>10.121231</td>\n",
       "      <td>4.370981</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>8.065226</td>\n",
       "      <td>2.487984</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>6.374166</td>\n",
       "      <td>1.368232</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@patch\n",
    "@contextmanager\n",
    "def no_bar(self:Learner):\n",
    "    \"Context manager that deactivates the use of progress bars\"\n",
    "    has_progress = hasattr(self, 'progress')\n",
    "    if has_progress: self.remove_cb(self.progress)\n",
    "    try: yield self\n",
    "    finally:\n",
    "        if has_progress: self.add_cb(ProgressCallback())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 15.748106002807617, 12.352150917053223, '00:00']\n",
      "[1, 13.818815231323242, 8.879858016967773, '00:00']\n",
      "[2, 11.650713920593262, 5.857329845428467, '00:00']\n",
      "[3, 9.595088005065918, 3.7397098541259766, '00:00']\n",
      "[4, 7.814438343048096, 2.327916145324707, '00:00']\n"
     ]
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "with learn.no_bar(): learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| hide\n",
    "#Check validate works without any training\n",
    "def tst_metric(out, targ): return F.mse_loss(out, targ)\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| hide\n",
    "#Check get_preds works without any training\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L16){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_fit\n",
       "\n",
       ">      ProgressCallback.before_fit ()\n",
       "\n",
       "Setup the master bar over the epochs"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L16){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_fit\n",
       "\n",
       ">      ProgressCallback.before_fit ()\n",
       "\n",
       "Setup the master bar over the epochs"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L24){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_epoch\n",
       "\n",
       ">      ProgressCallback.before_epoch ()\n",
       "\n",
       "Update the master bar"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L24){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_epoch\n",
       "\n",
       ">      ProgressCallback.before_epoch ()\n",
       "\n",
       "Update the master bar"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_epoch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L27){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_train\n",
       "\n",
       ">      ProgressCallback.before_train ()\n",
       "\n",
       "Launch a progress bar over the training dataloader"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L27){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_train\n",
       "\n",
       ">      ProgressCallback.before_train ()\n",
       "\n",
       "Launch a progress bar over the training dataloader"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L28){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_validate\n",
       "\n",
       ">      ProgressCallback.before_validate ()\n",
       "\n",
       "Launch a progress bar over the validation dataloader"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L28){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.before_validate\n",
       "\n",
       ">      ProgressCallback.before_validate ()\n",
       "\n",
       "Launch a progress bar over the validation dataloader"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L31){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_batch\n",
       "\n",
       ">      ProgressCallback.after_batch ()\n",
       "\n",
       "Update the current progress bar"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L31){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_batch\n",
       "\n",
       ">      ProgressCallback.after_batch ()\n",
       "\n",
       "Update the current progress bar"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_train\n",
       "\n",
       ">      ProgressCallback.after_train ()\n",
       "\n",
       "Close the progress bar over the training dataloader"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_train\n",
       "\n",
       ">      ProgressCallback.after_train ()\n",
       "\n",
       "Close the progress bar over the training dataloader"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L30){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_validate\n",
       "\n",
       ">      ProgressCallback.after_validate ()\n",
       "\n",
       "Close the progress bar over the validation dataloader"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L30){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_validate\n",
       "\n",
       ">      ProgressCallback.after_validate ()\n",
       "\n",
       "Close the progress bar over the validation dataloader"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L39){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_fit\n",
       "\n",
       ">      ProgressCallback.after_fit ()\n",
       "\n",
       "Close the master bar"
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L39){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### ProgressCallback.after_fit\n",
       "\n",
       ">      ProgressCallback.after_fit ()\n",
       "\n",
       "Close the master bar"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ShowGraphCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "class ShowGraphCallback(Callback):\n",
    "    \"Update a graph of training and validation loss\"\n",
    "    order,run_valid=65,False\n",
    "\n",
    "    def before_fit(self):\n",
    "        self.run = not hasattr(self.learn, 'lr_finder') and not hasattr(self, \"gather_preds\")\n",
    "        if not(self.run): return\n",
    "        self.nb_batches = []\n",
    "        assert hasattr(self.learn, 'progress')\n",
    "\n",
    "    def after_train(self): self.nb_batches.append(self.train_iter)\n",
    "\n",
    "    def after_epoch(self):\n",
    "        \"Plot validation loss in the pbar graph\"\n",
    "        if not self.nb_batches: return\n",
    "        rec = self.learn.recorder\n",
    "        iters = range_of(rec.losses)\n",
    "        val_losses = [v[1] for v in rec.values]\n",
    "        x_bounds = (0, (self.n_epoch - len(self.nb_batches)) * self.nb_batches[0] + len(rec.losses))\n",
    "        y_bounds = (0, max((max(Tensor(rec.losses)), max(Tensor(val_losses)))))\n",
    "        self.progress.mbar.update_graph([(iters, rec.losses), (self.nb_batches, val_losses)], x_bounds, y_bounds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>17.683565</td>\n",
       "      <td>10.431150</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>15.232769</td>\n",
       "      <td>7.056944</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>12.470916</td>\n",
       "      <td>4.382421</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>10.000675</td>\n",
       "      <td>2.574951</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>7.943449</td>\n",
       "      <td>1.464153</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| slow\n",
    "learn = synth_learner(cbs=ShowGraphCallback())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "(tensor([1.8955]), tensor([1.8955]), tensor([1.8955]))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "learn.predict(torch.tensor([[0.1]]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSVLogger -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "class CSVLogger(Callback):\n",
    "    \"Log the results displayed in `learn.path/fname`\"\n",
    "    order=60\n",
    "    def __init__(self, fname='history.csv', append=False):\n",
    "        self.fname,self.append = Path(fname),append\n",
    "\n",
    "    def read_log(self):\n",
    "        \"Convenience method to quickly access the log.\"\n",
    "        return pd.read_csv(self.path/self.fname)\n",
    "\n",
    "    def before_fit(self):\n",
    "        \"Prepare file with metric names.\"\n",
    "        if hasattr(self, \"gather_preds\"): return\n",
    "        self.path.parent.mkdir(parents=True, exist_ok=True)\n",
    "        self.file = (self.path/self.fname).open('a' if self.append else 'w')\n",
    "        self.file.write(','.join(self.recorder.metric_names) + '\\n')\n",
    "        self.old_logger,self.learn.logger = self.logger,self._write_line\n",
    "\n",
    "    def _write_line(self, log):\n",
    "        \"Write a line with `log` and call the old logger.\"\n",
    "        self.file.write(','.join([str(t) for t in log]) + '\\n')\n",
    "        self.file.flush()\n",
    "        os.fsync(self.file.fileno())\n",
    "        self.old_logger(log)\n",
    "\n",
    "    def after_fit(self):\n",
    "        \"Close the file and clean up.\"\n",
    "        if hasattr(self, \"gather_preds\"): return\n",
    "        self.file.close()\n",
    "        self.learn.logger = self.old_logger"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results are appended to an existing file if `append`, or they overwrite it otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* Turns off some styling */\n",
       "    progress {\n",
       "        /* gets rid of default border in Firefox and Opera. */\n",
       "        border: none;\n",
       "        /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
       "        background-size: auto;\n",
       "    }\n",
       "    progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
       "        background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
       "    }\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #F44336;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>15.606769</td>\n",
       "      <td>14.485189</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>13.840394</td>\n",
       "      <td>10.834929</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>11.842106</td>\n",
       "      <td>7.582738</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>9.937692</td>\n",
       "      <td>5.158300</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>8.244681</td>\n",
       "      <td>3.432087</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner(cbs=CSVLogger())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L101){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.read_log\n",
       "\n",
       ">      CSVLogger.read_log ()\n",
       "\n",
       "Convenience method to quickly access the log."
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L101){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.read_log\n",
       "\n",
       ">      CSVLogger.read_log ()\n",
       "\n",
       "Convenience method to quickly access the log."
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(CSVLogger.read_log)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = learn.csv_logger.read_log()\n",
    "test_eq(df.columns.values, learn.recorder.metric_names)\n",
    "for i,v in enumerate(learn.recorder.values):\n",
    "    test_close(df.iloc[i][:3], [i] + v)\n",
    "os.remove(learn.path/learn.csv_logger.fname)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L105){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.before_fit\n",
       "\n",
       ">      CSVLogger.before_fit ()\n",
       "\n",
       "Prepare file with metric names."
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L105){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.before_fit\n",
       "\n",
       ">      CSVLogger.before_fit ()\n",
       "\n",
       "Prepare file with metric names."
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(CSVLogger.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L120){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.after_fit\n",
       "\n",
       ">      CSVLogger.after_fit ()\n",
       "\n",
       "Close the file and clean up."
      ],
      "text/plain": [
       "---\n",
       "\n",
       "[source](https://github.com/fastai/fastai/blob/master/fastai/callback/progress.py#L120){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
       "\n",
       "### CSVLogger.after_fit\n",
       "\n",
       ">      CSVLogger.after_fit ()\n",
       "\n",
       "Close the file and clean up."
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show_doc(CSVLogger.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "from nbdev import nbdev_export\n",
    "nbdev_export()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "split_at_heading": true
  },
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
